﻿using System;
using System.Collections.Generic;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

// ReSharper disable CheckNamespace
namespace MugenInjection.Core.Components
// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the base class that implements <see cref="IConverter" /> interface.
    /// </summary>
    public abstract class ConverterBase : IConverter
    {
        #region Fields

        /// <summary>
        ///      Represents the key key for no cache binding.
        /// </summary>
        public const string NoCacheConverterKey = "~NoConverterCacheKey~";

        /// <summary>
        /// Gets the default priority of converter.
        /// </summary>
        protected const int DefaultPriority = 0;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="ConverterBase" /> class.
        /// </summary>
        protected ConverterBase(int priority, ServiceType serviceType)
        {
            Priority = priority;
            ServiceType = serviceType;
            IsUseBindingContextToGetSetResult = true;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the value that indicates that the converter use an <see cref="IBindingContext"/> to get or set result.
        /// </summary>
        public bool IsUseBindingContextToGetSetResult { get; set; }

        #endregion

        #region Implementation of IConverter

        /// <summary>
        ///     Gets the service type of converter.
        /// </summary>
        public ServiceType ServiceType { get; protected set; }

        /// <summary>
        ///     Gets the priority.
        /// </summary>
        public int Priority { get; private set; }

        /// <summary>
        ///     Gets the supported types.
        /// </summary>
        public abstract IEnumerable<Type> SupportedTypes { get; }

        /// <summary>
        ///     Tries to convert instance.
        /// </summary>
        /// <param name="bindingContext">The specified type to convert.</param>
        /// <param name="originalType">The result of convert.</param>
        /// <param name="convertAction">An delegate to convert instance.</param>
        /// <returns>
        ///     If <c>true</c> can be converted; otherwise <c>false</c>.
        /// </returns>
        public virtual ConverterResult TryConvert(IBindingContext bindingContext, out Type originalType,
                                                  out Func<object, object> convertAction)
        {
            var result = TryConvertInternal(bindingContext, out originalType, out convertAction);
            if (IsUseBindingContextToGetSetResult)
            {
                if (result == ConverterResult.SupportedWithoutCache)
                    SetNoCacheKey(bindingContext);
                if (result == ConverterResult.SupportedWithCache)
                    return GetCacheResult(bindingContext);
            }
            return result;
        }

        /// <summary>
        /// Clears cache, if any.
        /// </summary>
        public void ClearCache()
        {
            ClearCacheInternal();
        }

        /// <summary>
        ///     Occurs when the <c>SupportedTypes</c> changed.
        /// </summary>
        public event TypeChangedDelegate SupportedTypeChanged;

        #endregion

        #region Methods

        /// <summary>
        ///     Tries to convert instance.
        /// </summary>
        /// <param name="bindingContext">The specified type to convert.</param>
        /// <param name="originalType">The result of convert.</param>
        /// <param name="convertAction">A delegate to convert instance.</param>
        /// <returns>
        ///     If <c>true</c> can be converted; otherwise <c>false</c>.
        /// </returns>
        protected abstract ConverterResult TryConvertInternal(IBindingContext bindingContext, out Type originalType,
                                                   out Func<object, object> convertAction);

        /// <summary>
        /// Clears cache, if any.
        /// </summary>
        protected virtual void ClearCacheInternal()
        {

        }

        /// <summary>
        /// Sets the no cache key to <see cref="IBindingContext"/>.
        /// </summary>
        /// <param name="context">The specified <see cref="IBindingContext"/>.</param>
        protected void SetNoCacheKey(IBindingContext context)
        {
            if (context.SpecialParameters.ContainsKey(NoCacheConverterKey)) return;
            IDictionary<string, object> dictionary = InjectorUtilsInternal.GetDictionary(context);
            dictionary[NoCacheConverterKey] = true;
        }

        /// <summary>
        /// Gets the supported result using <see cref="IBindingContext"/>.
        /// </summary>
        /// <param name="context">The specified <see cref="IBindingContext"/>.</param>
        /// <returns>An instance of <see cref="ConverterResult"/>.</returns>
        private static ConverterResult GetCacheResult(IBindingContext context)
        {
            if (context.SpecialParameters.ContainsKey(NoCacheConverterKey))
                return ConverterResult.SupportedWithoutCache;
            return ConverterResult.SupportedWithCache;
        }

        /// <summary>
        ///     Raises the <c>SupportedTypeChanged</c> event.
        /// </summary>
        protected virtual void OnSupportedTypeChanged(TypeChangedAction action, Type type)
        {
            TypeChangedDelegate handler = SupportedTypeChanged;
            if (handler != null)
                handler(this, action, type);
        }

        #endregion

        #region Overrides of Object

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return string.Format("Converter with ServiceType = {0} and priority = {1}, to convert types {2}",
                                 ServiceType, Priority,
                                 InjectorUtilsInternal.FormatTypes(new List<Type>(SupportedTypes)));
        }

        #endregion
    }
}